﻿using System.Collections.Generic;
using System.Linq;
using MultiResolutioning.Interfaces;
using MultiResolutioning.Structures;
using MultiResolutioning.Collections;

namespace MultiResolutioning.Data
{
	/// <summary>
	/// Klasa reprezentuje siatke progresywna.
	/// </summary>
	public class ProgressiveMesh
	{
		public ISimplifier simplifier;
		public MeshData mesh;
		public List<Split> splits;
		private SortedVertexList sorted;

		/// <summary>
		/// Rozpoaczyna upraszczanie modelu korzystajac umieszczonego obiektu implementujacego ISimplifier.
		/// </summary>
		/// <param name="iterations">Liczba iteracji.</param>
		/// <param name="outtime">Czas wykonania.</param>
		public void Simplify(int iterations, out string outtime)
		{
			this.simplifier.Simplify(this, iterations, out outtime);
		}

		public ProgressiveMesh(MeshData mesh)
		{
			this.mesh = mesh;
			this.splits = new List<Split>();
			this.sorted = new SortedVertexList(mesh.vertices);
		}

		public ProgressiveMesh(MeshData mesh, int splitsCount)
		{
			this.mesh = mesh;
			this.splits = new List<Split>(splitsCount);
			this.sorted = new SortedVertexList(mesh.vertices);
		}

		public ProgressiveMesh(MeshData mesh, List<Split> splitsList)
		{
			this.mesh = mesh;
			this.splits = splitsList;
			this.sorted = new SortedVertexList(mesh.vertices);
		}

		/// <summary>
		/// Dodaje operacje dzielenia wierzcholka.
		/// </summary>
		/// <param name="item">The item.</param>
		public void AddSplit(Split item)
		{
			this.splits.Add(item);
		}

		/// <summary>
		/// Wykonuje operacje dzielena.
		/// </summary>
		/// <param name="iterations">Liczba iteracji.</param>
		public void DoSplits(int iterations)
		{
			if (this.splits.Count < iterations)
			{
				return;
			}

			for (int i = 0; i < iterations; ++i)
			{
				DoSplit();
			}
		}

		private void DoSplit()
		{
			// Poping last split operation.
			Split last = this.splits.Last();
			this.splits.RemoveAt(this.splits.Count - 1);

			// Finding vertex to split.
			//Vertex toSplit = this.mesh.vertices.Find(x => last.vertexIdToSplit == x.vertexId);
			Vertex toSplit = this.sorted.FindBinary(last.vertexIdToSplit);

			Vertex newVertex = new Vertex();

			// List of all needed vertices.
			List<Vertex> allVertices = new List<Vertex>(toSplit.neighbors);
			allVertices.Add(toSplit);
			allVertices.Add(newVertex);

			// Setting positions.
			newVertex.position = last.positionV1;
			toSplit.position = last.positionV2;

			// Setting ids for vertices.
			newVertex.vertexId = last.v1Id;
			toSplit.vertexId = last.v2Id;

			// Setting faces.
			List<Face> v1Faces = toSplit.faces.Where(x => last.faceIdsV1.Contains(x.faceId)).ToList();
			List<Face> v2Faces = toSplit.faces.Where(x => !last.faceIdsV1.Contains(x.faceId)).ToList();

			newVertex.faces = v1Faces;
			toSplit.faces = v2Faces;

			// Swaping old vertex to new vertex in faces.
			foreach (Face f in v1Faces)
			{
				for (int i = 0; i < 3; ++i)
				{
					if (f.vertices[i] == toSplit)
					{
						f.vertices[i] = newVertex;
						
						//break;
						continue;
					}

					// or creating neighbor connection between them.
					toSplit.neighbors.Remove(f.vertices[i]);
					newVertex.neighbors.Add(f.vertices[i]);
					f.vertices[i].neighbors.Remove(toSplit);
					f.vertices[i].neighbors.Add(newVertex);
				}
			}

			newVertex.neighbors.Add(toSplit);
			toSplit.neighbors.Add(newVertex);

			// Creating new faces.
			foreach (FaceSmall f in last.removedData)
			{
				Face face = new Face();
				face.faceId = f.faceId;

				face.vertices[0] = allVertices.Find(x => x.vertexId == f.verticesIds[0]);
				face.vertices[1] = allVertices.Find(x => x.vertexId == f.verticesIds[1]);
				face.vertices[2] = allVertices.Find(x => x.vertexId == f.verticesIds[2]);

				for (int i = 0; i < 3; ++i)
				{
					if (face.vertices[i] == null)
					{
						face.vertices[i] = mesh.vertices.Find(x => x.vertexId == f.verticesIds[i]);
					}
				}

				face.vertices[0].faces.Add(face);
				face.vertices[1].faces.Add(face);
				face.vertices[2].faces.Add(face);

				foreach (Vertex v in face.vertices)
				{
					if (v != newVertex && v != toSplit)
					{
						v.neighbors.Add(newVertex);
						v.neighbors.Add(toSplit);
						toSplit.neighbors.Add(v);
						newVertex.neighbors.Add(v);
						
						v.neighbors = v.neighbors.Distinct().ToList();
					}
				}

				this.mesh.faces.Add(face);
			}

			newVertex.neighbors = newVertex.neighbors.Distinct().ToList();
			toSplit.neighbors = toSplit.neighbors.Distinct().ToList();
			
			//this.mesh.vertices.Add(newVertex);
			this.mesh.vertices.RemoveAt(toSplit.InCollectionIndex);
			this.sorted.BinaryInsert(toSplit);
			this.sorted.BinaryInsert(newVertex);
			
		}
	}
}
